import { NextRequest, NextResponse } from "next/server";
// Increase timeout for Vercel Pro/Enterprise
export const maxDuration = 300;
// Define our tools
const tools = {
echo: {
name: "echo",
description: "Echo a message back",
inputSchema: {
type: "object",
properties: {
message: {
type: "string",
description: "The message to echo back"
}
},
required: ["message"]
}
},
getCurrentTime: {
name: "get-current-time",
description: "Get the current timestamp",
inputSchema: {
type: "object",
properties: {},
required: []
}
},
calculate: {
name: "calculate",
description: "Perform basic mathematical calculations",
inputSchema: {
type: "object",
properties: {
expression: {
type: "string",
description: "Mathematical expression to evaluate (e.g., '2 + 2', '10 * 5')"
}
},
required: ["expression"]
}
}
};
// Tool handlers
const toolHandlers = {
echo: async ({ message }: { message: string }) => {
return {
content: [
{
type: "text",
text: `Echo: ${message}`
}
]
};
},
"get-current-time": async () => {
const now = new Date();
return {
content: [
{
type: "text",
text: `Current time: ${now.toISOString()}\nTimestamp: ${now.getTime()}`
}
]
};
},
calculate: async ({ expression }: { expression: string }) => {
try {
// Simple calculator - only allow basic math operations for security
const sanitized = expression.replace(/[^0-9+\-*/().\s]/g, '');
if (sanitized !== expression) {
throw new Error("Invalid characters in expression");
}
// Use Function constructor for safe evaluation
const result = Function(`"use strict"; return (${sanitized})`)();
return {
content: [
{
type: "text",
text: `${expression} = ${result}`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error calculating "${expression}": ${error instanceof Error ? error.message : "Invalid expression"}`
}
]
};
}
}
};
// Handle MCP requests
async function handleMcpRequest(request: any) {
const { method, params } = request;
switch (method) {
case "tools/list":
return {
tools: [tools.echo, tools.getCurrentTime, tools.calculate]
};
case "tools/call":
const { name, arguments: args } = params;
const handler = toolHandlers[name as keyof typeof toolHandlers];
if (!handler) {
throw new Error(`Unknown tool: ${name}`);
}
return await handler(args || {});
case "initialize":
return {
protocolVersion: "2024-11-05",
capabilities: {
tools: {}
},
serverInfo: {
name: "custom-mcp-server",
version: "1.0.0"
}
};
default:
throw new Error(`Unknown method: ${method}`);
}
}
// HTTP handler
export async function POST(req: NextRequest) {
try {
const body = await req.json();
// Handle JSON-RPC request
const result = await handleMcpRequest(body);
return NextResponse.json({
jsonrpc: "2.0",
id: body.id,
result
});
} catch (error) {
console.error('MCP request error:', error);
return NextResponse.json({
jsonrpc: "2.0",
id: null,
error: {
code: -32603,
message: error instanceof Error ? error.message : "Internal error"
}
}, { status: 500 });
}
}
// GET handler for SSE transport
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const transport = searchParams.get('transport');
if (transport === 'sse') {
// For SSE, we need Redis configuration
const redisConfig = getRedisConfig();
if (!redisConfig) {
return NextResponse.json({
error: "SSE transport requires Redis configuration"
}, { status: 503 });
}
// Return SSE endpoint info
return NextResponse.json({
transport: "sse",
endpoint: "/sse",
status: "available"
});
}
// Default response
return NextResponse.json({
name: "Custom MCP Server",
transport: "http",
endpoints: {
http: "/mcp",
sse: "/sse"
},
tools: Object.keys(tools)
});
}
// Redis configuration helper
function getRedisConfig() {
if (process.env.UPSTASH_REDIS_REST_URL && process.env.UPSTASH_REDIS_REST_TOKEN) {
return {
type: "upstash" as const,
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN,
};
}
if (process.env.REDIS_URL) {
return {
type: "redis" as const,
url: process.env.REDIS_URL,
};
}
return null;
}